package com.ym.core.es.service;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.ReflectUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ym.common.constant.BaseEntity;
import com.ym.base.domain.MobileSearchParam;
import com.ym.common.utils.StringUtil;
import com.ym.core.es.annotation.GlobalWord;
import com.ym.core.es.config.ElasticsearchIndexProperties;
import com.ym.core.es.domain.index.BaseIdIndex;
import com.ym.core.es.domain.index.GlobalSearchWordIndex;
import com.ym.core.es.engine.SearchQueryEngine;
import com.ym.core.es.query.QueryParser;
import com.ym.core.utils.EnvUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.domain.Page;
import org.springframework.data.elasticsearch.annotations.Document;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


/**
 * 索引表，高亮文字提取接口
 * <p>
 * 注入条件pageHelperProperties, 为了使分页有效
 *
 * @author: 李涛
 * @version: 2020年01月07日 14:17
 */
@Slf4j
@DependsOn("pageHelperProperties")
public abstract class AbstractHeightWordOperationService<T extends BaseIdIndex, E extends BaseEntity> implements ApplicationRunner {

	@Autowired
	private SearchQueryEngine searchQueryEngine;

	@Autowired
	private GlobalSearchWordIndexService globalSearchWordIndexService;

	@Autowired
	private ElasticsearchIndexProperties setting;

	@Autowired
	private EnvUtil envUtil;

	/**
	 * 数据类型标志
	 */
	public String dataType;

	/**
	 * 数据相关类
	 */
	public Class<T> dataTypeClass;

	{
		if (dataTypeClass == null) {
			dataTypeClass = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
		}
		Document document = dataTypeClass.getAnnotation(Document.class);
		this.dataType = document.indexName();
	}

	/**
	 * 获取要初始化的数据
	 *
	 * @return
	 */
	public abstract List<E> getData();

	/**
	 * 业务类转换为索引数据类
	 */
	public abstract T beanCovert(E data);

	/**
	 * 子类关键词查询，
	 * 需要实现的方法
	 *
	 * @return
	 */
	protected abstract QueryParser buildSearchIdsConditions(MobileSearchParam args);

	/**
	 * 解决分页插件未注入生效的问题
	 *
	 * @param args
	 * @throws Exception
	 */
	@Override
	public void run(ApplicationArguments args) throws Exception {
		// 系统启动重建索引
		if (setting.isStartedReload()) {
			init();
		}
	}

	/**
	 * 初始化前置方法
	 */
	public void init() {
		try {
			// 删除索引
			log.info("{} 删除全表索引", dataTypeClass.getSimpleName());
			searchQueryEngine.deleteIndex(dataTypeClass);
			log.info("{} 初始化索引...", dataTypeClass.getSimpleName());
			searchQueryEngine.createIndex(dataTypeClass, setting.getSetting());
			log.info("{} 删除全部高亮索引", dataTypeClass.getSimpleName());
			this.globalSearchWordIndexService.deleteWords(dataType, dataTypeClass);
			// 获取数据
			int pageNum = 1;
			int pageSize = 1000;
			List<T> initData = getIndexData(pageNum, pageSize);
			while (!initData.isEmpty()) {
				log.info("第{}页,重建表索引个数为:{}", pageNum, initData.size());
				// 1.索引建立
				searchQueryEngine.saveOrUpdate(initData);
				// 2.添加全局高亮结果索引
				refreshHeightWord(initData);
				initData = getIndexData(++pageNum, pageSize);
			}

		} catch (Exception e) {
			log.error(ExceptionUtil.getRootCauseMessage(e));
		}
	}

	/**
	 * 需要抽取高亮关键词
	 *
	 * @return
	 */
	private void refreshHeightWord(List<T> indexData) {
		List<GlobalSearchWordIndex> heightWordIndices = new ArrayList<>();
		for (T t : indexData) {
			List<GlobalSearchWordIndex> indexs = beanCovertHeightWord(t);
			heightWordIndices.addAll(indexs);
		}
		log.info("{} 重建高亮索引 {}个", dataTypeClass.getSimpleName(), heightWordIndices.size());
		this.globalSearchWordIndexService.insertBatch(heightWordIndices);
	}

	/**
	 * 获取要初始化的索引数据
	 *
	 * @return
	 */
	public List<T> getIndexData(int pageNum, int pageSize) {
		PageHelper.startPage(pageNum, pageSize);
		List<E> data = getData();
		PageInfo<E> pageInfo = new PageInfo<>(data);
		List<T> indexData = pageInfo.getList().stream().map(n -> beanCovert(n)).collect(Collectors.toList());
		return indexData;
	}

	/**
	 * 根据关键词查询数据集合
	 *
	 * @param args 参数
	 * @return
	 */
	public List<Long> searchByKeywordOfIds(MobileSearchParam args) {
		String keyword = args.getKeyword();
		if (StringUtil.isBlank(keyword)) {
			// 输入字符无效返回空集合
			return Collections.emptyList();
		}
		// ----------需要改动的业务代码---------
		QueryParser query = null;
		try {
			query = buildSearchIdsConditions(args);
		} catch (Exception e) {
			log.error(ExceptionUtil.stacktraceToString(e));
		}
		//----------需要改动的业务代码----------
		// 因为lucene分页和mysql分页不能同时使用，所以只取前20条匹配度高的
		Page<T> data = this.searchQueryEngine.queryForPage(query, dataTypeClass);
		if (data == null || data.isEmpty()) {
			// 如果没查出数据返回空集合
			return Collections.emptyList();
		} else {
			return data.stream().map(n -> Long.parseLong(n.getId())).collect(Collectors.toList());
		}
	}

	/**
	 * 抽取索引对象中,需要全局保存的关键词
	 *
	 * @param data
	 * @return
	 */
	public List<GlobalSearchWordIndex> beanCovertHeightWord(T data) {
		List<GlobalSearchWordIndex> heightWordIndexList = new ArrayList<>();
		String tableName = StringUtil.toUnderScoreCase(data.getClass().getSimpleName());
		Map<String, GlobalWord> globalWordField = getGlobalWordField(dataTypeClass);
		globalWordField.forEach((fieldName, globalWord) -> {
			boolean mul = globalWord.mul();
			String fieldValue = String.valueOf(ReflectUtil.getFieldValue(data, fieldName));
			// 如果是多关键字
			if (mul) {
				String delimiter = globalWord.delimiter();
				if (StringUtil.isBlank(fieldValue)) {
					return;
				}
				String[] splitKeywords = fieldValue.split(delimiter);
				for (String splitKeyword : splitKeywords) {
					GlobalSearchWordIndex globalSearchWordIndex = this.globalSearchWordIndexService.buildGlobalSearchWordIndex(tableName, dataType, data, fieldName, splitKeyword);
					heightWordIndexList.add(globalSearchWordIndex);
				}
			} else {
				// 否则单关键字
				GlobalSearchWordIndex globalSearchWordIndex = this.globalSearchWordIndexService.buildGlobalSearchWordIndex(tableName, dataType, data, fieldName, fieldValue);
				heightWordIndexList.add(globalSearchWordIndex);
			}

		});
		return heightWordIndexList;
	}

	/**
	 * 删除
	 *
	 * @return
	 */
	public void deleteAndSwapHeight(Long... ids) {
		if(ids != null && ids.length > 0){
			List<String> collect = Arrays.stream(ids).map(id -> String.valueOf(id)).collect(Collectors.toList());
			String[] idStr = collect.toArray(new String[collect.size()]);
			searchQueryEngine.deleteIndexByDocIds(dataTypeClass,idStr);
			// 删除全局关键词的索引
			globalSearchWordIndexService.deleteWords(dataType,dataTypeClass,ids);
		}
	}

	/**
	 * 修改
	 *
	 * @return
	 */
	public void saveOrUpdate(E e) {
		T t = this.beanCovert(e);
		searchQueryEngine.saveOrUpdate(t);
		// 全局关键词词刷新
		updateHeightWord(t);
	}


	/**
	 * 根据业务对象新增
	 *
	 * @param list
	 * @return
	 */
	public void saveOrUpdate(List<E> list) {
		List<T> collect = list.stream().map(n -> this.beanCovert(n)).collect(Collectors.toList());
		searchQueryEngine.saveOrUpdate(collect);
		for (T t : collect) {
			updateHeightWord(t);
		}
	}

	/**
	 * 更新关键词
	 *
	 * @param data
	 * @return
	 */
	private void updateHeightWord(T data) {
		Long id = (Long) ReflectUtil.getFieldValue(data, "id");
		this.globalSearchWordIndexService.deleteWord(dataType, dataTypeClass, id);
		List<GlobalSearchWordIndex> heightWordIndices = beanCovertHeightWord(data);
		this.globalSearchWordIndexService.insertBatch(heightWordIndices);
	}

	/**
	 * 获取高亮注解
	 *
	 * @param indexClass
	 * @param <T>
	 * @return
	 */
	private <T extends BaseIdIndex> Map<String, GlobalWord> getGlobalWordField(Class<T> indexClass) {
		Map<String, GlobalWord> map = new HashMap<>();
		Field[] fields = ReflectUtil.getFields(indexClass);
		for (Field field : fields) {
			GlobalWord heightWord = field.getAnnotation(GlobalWord.class);
			if (heightWord != null) {
				map.put(field.getName(), heightWord);
			}
		}
		return map;
	}

	/**
	 * 刷新索引
	 */
	protected void refreshIndex() {
		this.init();
	}

}
